package rubik;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Stack;

import rubik.crossover.ICrossOverFunc;
import rubik.fitness.IFitnessFunc;
import rubik.mutators.ICubeSolutionMutator;
import rubik.seqs.ISequenceGenerator;
import rubik.seqs.Sequence;
import evo.IInstance;

public class CubeSolution implements IInstance, ICubeSolution {
	
	private static Random r = new Random();
	private static final int MAX_LENGTH = 20;
	private static final int MAX_FITNESS = 54;
	private ArrayList<Sequence> _seqs;
	private Cube _cube;
	private ISequenceGenerator _seqGenarator;
	private ICubeSolutionMutator _mutator;
	private IFitnessFunc _fitnessFunc;
	private ICrossOverFunc _crossOverFunc;
	private double _currentFitness;
	private List<Double> _historicalFitness;
	
	/**
	 * This copy constructor we use mostly for mutation on current solution
	 * Its specificity that it remember its previous fitness values
	 * i.e. remember if somewhere it had a good value
	 * @param sol
	 */
	public CubeSolution(ICubeSolution sol)
	{
		this._cube = sol.getCube();
		this._seqs = new ArrayList<Sequence>();
		this._mutator = sol.getMutator();
		this._seqGenarator = sol.getSeqGenerator();
		this._crossOverFunc = sol.getCrossoverFunc();
		this._fitnessFunc = sol.getFitnessFunc();
		this._historicalFitness = new LinkedList<Double>(sol.getHistoricalFitness());
		for (Sequence seq: sol.getSeqs()){ 
			this._seqs.add(new Sequence(seq));
		}
		
	}
	
	/**
	 * This constructor we use mostly for cross over functions
	 * In contrast with other copy constructor it doesn't remember its previous 
	 * fitness values, and copy only generators from its parent
	 */
	public CubeSolution(Cube cube, ISequenceGenerator seqGenerator, ICubeSolutionMutator mutator, IFitnessFunc fitnessFunc, ICrossOverFunc crossOverFnc)
	{
		this._seqGenarator = seqGenerator;
		this._mutator = mutator;
		this._seqs = new ArrayList<Sequence>();
		this._cube = cube;
		this._fitnessFunc = fitnessFunc;
		this._crossOverFunc = crossOverFnc;
		this._historicalFitness = new LinkedList<Double>();
	}
	
	public void randomize() {
		int len = r.nextInt(MAX_LENGTH) + 1;
		for (int i = 0; i < len; i++) {
			Sequence seq = this.randomSeq();
			this._seqs.add(seq);
		}
	}
		
	public double getMaxFitness()
	{
		return this._fitnessFunc.getMaxFitness();
	}

	@Override
	public IInstance mutate() {
		CubeSolution newsol = new CubeSolution(this);
		this._mutator.mutate(newsol);
		return newsol;
	}
	
	@Override
	public IInstance crossover(IInstance other) {
		return this._crossOverFunc.applyCrossOver(this, (CubeSolution)other);
	}
	
	private Sequence randomSeq() {
		return this._seqGenarator.createRandom();
	}
	
	public ArrayList<Sequence> getSeqs() {
		return this._seqs;
	}
	
	@Override
	public int compareTo(IInstance o) {
		assert o instanceof CubeSolution;
		double res = this.getFitness() - o.getFitness(); 
		return (int)Math.signum(res);
	}

	@Override
	public Cube getCube() {
		return this._cube;
	}

	@Override
	public ISequenceGenerator getSeqGenerator() {
		return this._seqGenarator;
	}

	@Override
	public ICubeSolutionMutator getMutator() {
		return this._mutator;
	}
	
	public String toString()
	{
		if (this._seqs.size()==0)
			return "";
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < this._seqs.size()-1; i++) {
			sb.append(this._seqs.get(i).toString() +",");
		}
		sb.append(this._seqs.get(this._seqs.size()-1).toString());
		return sb.toString();
	}
	
	@SuppressWarnings("unused")
	private void trimSeqs()
	{
		Stack<EMove> stack = new Stack<EMove>();
		for (Sequence seq : this._seqs)
		{
			for (EMove emove: seq.getMoves())
			{
				if (!stack.empty())
				{
					if (stack.peek() == emove.inverse())
					{
						stack.pop();
						continue;
					}
				}
				stack.push(emove);
			}
		}
	}
	
	@Override
	public IFitnessFunc getFitnessFunc(){
		return this._fitnessFunc;
	}

	@Override
	public ISequenceGenerator getSequenceGenerator() {
		return this._seqGenarator;
	}

	@Override
	public ICrossOverFunc getCrossoverFunc() {
		return this._crossOverFunc;
	}

	@Override
	public double getFitness() {
		return this._currentFitness;
	}
	
	@Override
	public List<Double> getHistoricalFitness() {
		return this._historicalFitness.subList(_historicalFitness.size()-10, _historicalFitness.size());
	}

	@Override
	public void calculateFitness() {
		this._cube.reset();
		for (Sequence seq : this._seqs)
		{			
			this._cube.applySeq(seq);
		}
		
		this._currentFitness = this._fitnessFunc.getFitness(this._cube);
		this._historicalFitness.add(this._currentFitness);
	}

}
